package com.yaoyong.yy_demo.module.poi;

import org.apache.poi.hssf.usermodel.DVConstraint;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFDataValidation;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.formula.functions.T;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellRangeAddressList;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

/**
 * ExcelUtil工具类实现功能:
 * 导出时传入list<T>,即可实现导出为一个excel,其中每个对象Ｔ为Excel中的一条记录.
 * 导入时读取excel,得到的结果是一个list<T>.T是自己定义的对象.
 * 需要导出的实体对象只需简单配置注解就能实现灵活导出,通过注解您可以方便实现下面功能:
 * 1.实体属性配置了注解就能导出到excel中,每个属性都对应一列.
 * 2.列名称可以通过注解配置.
 * 3.导出到哪一列可以通过注解配置.
 * 4.鼠标移动到该列时提示信息可以通过注解配置.
 * 5.用注解设置只能下拉选择不能随意填写功能.
 * 6.用注解设置是否只导出标题而不导出内容,这在导出内容作为模板以供用户填写时比较实用.
 *
 * @author 姚勇
 */
public class ExcelUtil {
    private static Logger logger = LoggerFactory.getLogger(ExcelUtil.class);
    private final static String XLS = "xls";
    private final static String XLSX = "xlsx";
    private final static Integer sheetMaxRows = 65535;

    private ExcelUtil() {
    }

    /**
     * 读入excel文件，解析后返回
     *
     * @param file      excel文件
     * @param sheetName 工作表名,不指定默认读第一个有数据的工作表
     * @return 每行中的值作为一个数组，所有行作为一个集合返回
     * @throws IOException
     */
    public static List<String[]> importExcel(MultipartFile file, String sheetName,InputStream inputStream) throws IOException {
        if (file != null) {
            //检查文件
            checkFile(file);
        }
        //获得Workbook工作薄对象
        Workbook workbook = getWorkBook(file,inputStream);
        //创建返回对象，把每行中的值作为一个数组，所有行作为一个集合返回
        List<String[]> list = new ArrayList<>();
        if (workbook != null) {
            for (int sheetNum = 0; sheetNum < workbook.getNumberOfSheets(); sheetNum++) {
                //获得当前sheet工作表
                Sheet sheet;
                if (sheetName != null) {
                    sheet = workbook.getSheet(sheetName);
                } else {
                    sheet = workbook.getSheetAt(sheetNum);
                }
                if (sheet != null) {
                    getList(sheet, list);
                    break;
                }
            }
            workbook.close();
        }
        return list;
    }

    private static void getList(Sheet sheet, List<String[]> list) {
        //获得当前sheet的开始行
        int firstRowNum = sheet.getFirstRowNum();
        //获得当前sheet的结束行
        int lastRowNum = sheet.getLastRowNum();
        //循环除了第一行的所有行
        for (int rowNum = firstRowNum + 1; rowNum <= lastRowNum; rowNum++) {
            //获得当前行
            Row row = sheet.getRow(rowNum);
            if (row == null) {
                continue;
            }
            //获得当前行的开始列
            int firstCellNum = row.getFirstCellNum();
            //获得当前行的列数
            int lastCellNum = row.getPhysicalNumberOfCells();
            String[] cells = new String[lastCellNum];
            //循环当前行
            for (int cellNum = firstCellNum; cellNum < lastCellNum; cellNum++) {
                Cell cell = row.getCell(cellNum);
                cells[cellNum] = getCellValue(cell);
            }
            list.add(cells);
        }
    }

    /**
     * 校验文件
     *
     * @param file
     * @throws IOException
     */
    private static void checkFile(MultipartFile file) throws IOException {
        //判断文件是否存在
        if (null == file) {
            logger.error("文件不存在！");
            throw new FileNotFoundException("文件不存在！");
        }
        //获得文件名
        String fileName = file.getOriginalFilename();
        //判断文件是否是excel文件
        if (!fileName.endsWith(XLS) && !fileName.endsWith(XLSX)) {
            logger.error(fileName + "不是excel文件");
            throw new IOException(fileName + "不是excel文件");
        }
    }

    /**
     * 根据文件后缀名创建对应工作簿
     *
     * @param file
     * @return
     */
    private static Workbook getWorkBook(MultipartFile file,InputStream inputStream) {
        if (file == null && inputStream !=null) {
            try {
                return new XSSFWorkbook(inputStream);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        //获得文件名
        String fileName = file.getOriginalFilename();
        //创建Workbook工作薄对象，表示整个excel
        Workbook workbook = null;
        try {
            //获取excel文件的io流
            InputStream is = file.getInputStream();
            //根据文件后缀名不同(xls和xlsx)获得不同的Workbook实现类对象
            if (fileName.endsWith(XLS)) {
                //2003
                workbook = new HSSFWorkbook(is);
            } else if (fileName.endsWith(XLSX)) {
                //2007以上
                workbook = new XSSFWorkbook(is);
            }
        } catch (IOException e) {
            logger.info(e.getMessage());
        }
        return workbook;
    }

    /**
     * 获取cell中的值
     *
     * @param cell
     * @return
     */
    private static String getCellValue(Cell cell) {
        String cellValue = "";
        if (cell == null) {
            return cellValue;
        }
        //把数字当成String来读，避免出现1读成1.0的情况
        if (cell.getCellType() == CellType.NUMERIC) {
            cell.setCellType(CellType.STRING);
        }
        //判断数据的类型
        switch (cell.getCellType()) {
            //数字
            case NUMERIC:
                cellValue = String.valueOf(cell.getNumericCellValue());
                break;
            //字符串
            case STRING:
                cellValue = String.valueOf(cell.getStringCellValue());
                break;
            //Boolean
            case BOOLEAN:
                cellValue = String.valueOf(cell.getBooleanCellValue());
                break;
            //公式
            case FORMULA:
                cellValue = String.valueOf(cell.getCellFormula());
                break;
            //空值
            case BLANK:
                cellValue = "";
                break;
            //故障
            case ERROR:
                cellValue = "非法字符";
                break;
            case _NONE:
                cellValue = "未知类型";
                break;
            default:
                break;
        }
        return cellValue;
    }

    /**
     * 导出Excel
     *
     * @param sheetName 工作表名称
     * @param title     标题 {"名称","性别"}
     * @param content   内容 {{"李四","男"},{"张三","女"}}
     * @param showRows  每个sheet中显示多少行数据,最多65536行
     * @param wb        Workbook 对象
     * @return HSSFWorkbook / XSSFWorkbook 对象,默认返回HSSFWorkbook
     */
    public static Workbook exportExcel(String sheetName, String[] title, String[][] content, Integer showRows, Workbook wb) {

        // 第一步，默认创建一个  2003 HSSFWorkbook，对应一个Excel文件
        if (wb == null) {
            wb = new HSSFWorkbook();
        }
        // excel2003中每个sheet中最多有65536行,为避免产生错误所以加这个逻辑.
        if (showRows == null || showRows > sheetMaxRows || showRows < 1) {
            showRows = sheetMaxRows;
        }
        // 取出一共有多少个sheet.
        double sheetNo = Math.ceil((double) content.length / showRows);
        for (int index = 0; index < sheetNo; index++) {
            // 第二步，在workbook中添加一个sheet,对应Excel文件中的sheet
            Sheet sheet = wb.createSheet(sheetName + index);

            // 第三步，在sheet中添加表头第0行,注意2003版本poi对Excel的行数列数有限制不超过65536
            Row row = sheet.createRow(0);

            // 第四步，创建单元格，并设置值表头 设置表头居中
            CellStyle style = wb.createCellStyle();
            // 创建一个居中格式
            style.setAlignment(HorizontalAlignment.CENTER);

            //声明列对象
            Cell cell;

            //创建标题
            for (int i = 0; i < title.length; i++) {
                cell = row.createCell(i);
                cell.setCellValue(title[i]);
                cell.setCellStyle(style);
            }

            int startNo = index * showRows;
            int endNo = Math.min(startNo + showRows, content.length);
            //创建内容
            for (int i = 0; startNo < endNo; startNo++, i++) {
                row = sheet.createRow(i + 1);
                for (int j = 0; j < content[startNo].length; j++) {
                    //将内容按顺序赋给对应的列对象
                    cell = row.createCell(j);
                    cell.setCellStyle(style);
                    cell.setCellValue(content[startNo][j]);
                }
            }
        }
        return wb;
    }

    /**
     * 把list集合中的数据导出到excel表单中
     *
     * @param sheetName 工作表的名称
     * @param sheetSize 每个工作表中数据的行数,此数值必须小于65536
     * @return HSSFWorkbook
     */
    public static HSSFWorkbook exportExcel(List<T> list, String sheetName, int sheetSize, Class<T> clazz) throws IllegalAccessException {

        // 得到所有定义字段
        Field[] allFields = clazz.getDeclaredFields();
        List<Field> fields = new ArrayList<>();
        // 得到所有field并存放到一个list中.
        for (Field field : allFields) {
            if (field.isAnnotationPresent(ExcelInfo.class)) {
                fields.add(field);
            }
        }

        // 创建工作薄
        HSSFWorkbook workbook = new HSSFWorkbook();
        // excel2003中每个sheet中最多有65536行,为避免产生错误所以加这个逻辑.
        if (sheetSize > sheetMaxRows || sheetSize < 1) {
            sheetSize = sheetMaxRows;
        }
        // 取出一共有多少个sheet.
        double sheetNo = Math.ceil(list.size() / sheetSize);
        for (int index = 0; index <= sheetNo; index++) {
            // 产生工作表对象
            HSSFSheet sheet = workbook.createSheet();
            // 设置工作表的名称.
            workbook.setSheetName(index, sheetName + index);
            //创建一行
            HSSFRow row = sheet.createRow(0);
            // 产生单元格
            HSSFCell cell;
            // 写入各个字段的列头名称
            for (int i = 0; i < fields.size(); i++) {
                Field field = fields.get(i);
                ExcelInfo attr = field.getAnnotation(ExcelInfo.class);
                // 获得列号
                int col = getExcelCol(attr.column());
                // 创建列
                cell = row.createCell(col);
                // 设置列中写入内容为String类型
                cell.setCellType(CellType.STRING);
                // 写入列名
                cell.setCellValue(attr.name());

                // 如果设置了提示信息则鼠标放上去提示.
                if (!attr.prompt().trim().equals("")) {
                    // 这里默认设了2-101列提示.
                    setHSSFPrompt(sheet, "", attr.prompt(), 1, 100, col, col);
                }
                // 如果设置了combo属性则本列只能选择不能输入
                if (attr.combo().length > 0) {
                    // 这里默认设了2-101列只能选择不能输入.
                    setHSSFValidation(sheet, attr.combo(), 1, 100, col, col);
                }
            }

            int startNo = index * sheetSize;
            int endNo = Math.min(startNo + sheetSize, list.size());
            // 写入各条记录,每条记录对应excel表中的一行
            for (int i = startNo; i < endNo; i++) {
                row = sheet.createRow(i + 1 - startNo);
                // 得到导出对象.
                T vo = list.get(i);
                for (int j = 0; j < fields.size(); j++) {
                    // 获得field.
                    Field field = fields.get(j);
                    // 设置实体类私有属性可访问
                    field.setAccessible(true);
                    ExcelInfo attr = field.getAnnotation(ExcelInfo.class);
                    // 根据ExcelVOAttribute中设置情况决定是否导出,有些情况需要保持为空,希望用户填写这一列.
                    if (attr.isExport()) {
                        // 创建cell
                        cell = row.createCell(getExcelCol(attr.column()));
                        cell.setCellType(CellType.STRING);
                        // 如果数据存在就填入,不存在填入空格.
                        cell.setCellValue(field.get(vo) == null ? "" : String.valueOf(field.get(vo)));
                    }

                }
            }

        }
        return workbook;

    }

    /**
     * 将EXCEL中A,B,C,D,E列映射成0,1,2,3
     *
     * @param col
     */
    private static int getExcelCol(String col) {
        col = col.toUpperCase();
        // 从-1开始计算,字母重1开始运算。这种总数下来算数正好相同。
        int count = -1;
        char[] cs = col.toCharArray();
        for (int i = 0; i < cs.length; i++) {
            count += (cs[i] - 64) * Math.pow(26, cs.length - 1 - i);
        }
        return count;
    }

    /**
     * 设置单元格上提示
     *
     * @param sheet         要设置的sheet.
     * @param promptTitle   标题
     * @param promptContent 内容
     * @param firstRow      开始行
     * @param endRow        结束行
     * @param firstCol      开始列
     * @param endCol        结束列
     * @return 设置好的sheet.
     */
    private static HSSFSheet setHSSFPrompt(HSSFSheet sheet, String promptTitle,
                                           String promptContent, int firstRow, int endRow, int firstCol,
                                           int endCol) {
        // 构造constraint对象
        DVConstraint constraint = DVConstraint
                .createCustomFormulaConstraint("DD1");
        // 四个参数分别是：起始行、终止行、起始列、终止列
        CellRangeAddressList regions = new CellRangeAddressList(firstRow,
                endRow, firstCol, endCol);
        // 数据有效性对象
        HSSFDataValidation data_validation_view = new HSSFDataValidation(
                regions, constraint);
        data_validation_view.createPromptBox(promptTitle, promptContent);
        sheet.addValidationData(data_validation_view);
        return sheet;
    }

    /**
     * 设置某些列的值只能输入预制的数据,显示下拉框.
     *
     * @param sheet    要设置的sheet.
     * @param textList 下拉框显示的内容
     * @param firstRow 开始行
     * @param endRow   结束行
     * @param firstCol 开始列
     * @param endCol   结束列
     * @return 设置好的sheet.
     */
    private static HSSFSheet setHSSFValidation(HSSFSheet sheet,
                                               String[] textList, int firstRow, int endRow, int firstCol,
                                               int endCol) {
        // 加载下拉列表内容
        DVConstraint constraint = DVConstraint.createExplicitListConstraint(textList);
        // 设置数据有效性加载在哪个单元格上,四个参数分别是：起始行、终止行、起始列、终止列
        CellRangeAddressList regions = new CellRangeAddressList(firstRow,
                endRow, firstCol, endCol);
        // 数据有效性对象
        HSSFDataValidation data_validation_list = new HSSFDataValidation(
                regions, constraint);
        sheet.addValidationData(data_validation_list);
        return sheet;
    }
}